package com.sri.lucene.search;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.HitCollector;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.PriorityQueue;

import com.sri.lucene.index.IndexManager;
import com.sri.model.home.Home;

/**
 * A HitCollector that retrieves a specific page of results
 * 
 * @author skodali
 */
public class HitPageCollector extends HitCollector {

	public HitPageCollector() {
	}

	// Demo code showing pagination
	public static Map<String, Object> search(String searchWord, int pageStart,
			int pageMax) throws Exception {
		Map<String, Object> map = new HashMap<String, Object>();
		List<Home> searchResult = new ArrayList<Home>(50);
		if (false == IndexManager.ifIndexExist()) {
			try {
				IndexManager.rebuildIndexes();
			} catch (IOException e) {
				e.printStackTrace();
				return null;
			}
		}

		IndexSearcher s = new IndexSearcher(IndexManager.getIndexDir());
		Query q = new TermQuery(new Term("content", searchWord));

		// Retrieve page 1 (hits pageStart-pageMax)
		HitPageCollector hpc = new HitPageCollector(pageStart, pageMax);
		s.search(q, hpc);
		ScoreDoc[] sd = hpc.getScores();
		System.out.println("Hits " + hpc.getStart() + " - " + hpc.getEnd()
				+ " of" + hpc.getTotalAvailable());
		Home resultBean = null;
		Document doc = null;
		int length = sd.length;
		for (int i = 0; i < length; i++) {
			doc = s.doc(sd[i].doc);
			resultBean = new Home();
			resultBean.setId(Integer.parseInt(doc.get("id")));
			resultBean.setName(doc.get("name"));
			resultBean.setRent(Integer.parseInt(doc.get("rent")));
			resultBean.setZipCode(doc.get("zip"));
			resultBean.setCityName(doc.get("city"));
			resultBean.setStateName(doc.get("stat"));
			resultBean.setPetsAllowed(Boolean.getBoolean((doc.get("pets"))));
			resultBean.setMessage(doc.get("desc"));
			resultBean.setNoOfMembersAllowed(Integer.parseInt(doc.get("mems")));
			resultBean.setMaintenanceCost(Integer.parseInt(doc.get("cost")));
			resultBean.setRentNegotiable(Boolean.getBoolean(doc.get("nego")));
			resultBean.setTypeId(Integer.parseInt(doc.get("type")));
			searchResult.add(resultBean);
		}
		s.close();
		map.put("list", searchResult);
		map.put("total", hpc.getTotalAvailable());
		return map;
	}

	int nDocs;
	PriorityQueue hq;
	float minScore = 0.0f;
	int totalHits = 0;
	int start;
	int maxNumHits;
	int totalInThisPage;

	public HitPageCollector(int start, int maxNumHits) {
		this.nDocs = start + maxNumHits;
		this.start = start;
		this.maxNumHits = maxNumHits;
		hq = new HitQueue(nDocs);
	}

	public void collect(int doc, float score) {
		totalHits++;
		if ((hq.size() < nDocs) || (score >= minScore)) {
			ScoreDoc scoreDoc = new ScoreDoc(doc, score);
			hq.insert(scoreDoc); // update hit queue
			minScore = ((ScoreDoc) hq.top()).score; // reset minScore
		}
		totalInThisPage = hq.size();
	}

	public ScoreDoc[] getScores() {
		// just returns the number of hits required from the required start
		// point
		/*
		 * So, given hits: 1234567890 and a start of 2 + maxNumHits of 3 should
		 * return: 234 or, given hits 12 should return 2 and so, on.
		 */
		if (start <= 0) {
			throw new IllegalArgumentException("Invalid start :" + start
					+ " - start should be >=1");
		}
		int numReturned = Math.min(maxNumHits, (hq.size() - (start - 1)));
		if (numReturned <= 0) {
			return new ScoreDoc[0];
		}
		ScoreDoc[] scoreDocs = new ScoreDoc[numReturned];
		ScoreDoc scoreDoc;
		for (int i = hq.size() - 1; i >= 0; i--) // put docs in array, working
		// backwards from lowest
		// count
		{
			scoreDoc = (ScoreDoc) hq.pop();
			if (i < (start - 1)) {
				break; // off the beginning of the results array
			}
			if (i < (scoreDocs.length + (start - 1))) {
				scoreDocs[i - (start - 1)] = scoreDoc; // within scope of
				// results array
			}
		}
		return scoreDocs;
	}

	public int getTotalAvailable() {
		return totalHits;
	}

	public int getStart() {
		return start;
	}

	public int getEnd() {
		return start + totalInThisPage - 1;
	}

	public class HitQueue extends PriorityQueue {
		public HitQueue(int size) {
			initialize(size);
		}

		public final boolean lessThan(Object a, Object b) {
			ScoreDoc hitA = (ScoreDoc) a;
			ScoreDoc hitB = (ScoreDoc) b;
			if (hitA.score == hitB.score)
				return hitA.doc > hitB.doc;
			else
				return hitA.score < hitB.score;
		}
	}
}